﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Microsoft.Forums.OfflineStore;
using Microsoft.Forums.Tests.HelperTools;
using Microsoft.Forums.Tests.HelperTools.Stubs;
using Microsoft.Forums.ForumsClient.Models;
using System.Windows.Threading;
using System.Threading;
using System.IO;
using System.ComponentModel;
using Microsoft.Forums.ForumsClient;

namespace ForumsClientUnitTests
{
    /// <summary>
    /// Test the PostContainer class
    /// </summary>
    [TestFixture]
    public class ThreadedPostModelTests
    {
        // Stubs
        ConnectionStub connectionStub;
        SyncControllerStub syncStub;
        ForumsProviderStub providerStub;
        SubscribedForumsCollection subscribedForums;

        /// <summary>
        /// Initialized the database
        /// </summary>
        [TestFixtureSetUp]
        public void SetupFixture()
        {
            DatabaseHelper.SetupDatabase();
        }

        /// <summary>
        /// Setup stubs and datamodel before each test
        /// </summary>
        [SetUp]
        public void Setup()
        {
            connectionStub = new ConnectionStub();
            syncStub = new SyncControllerStub();
            subscribedForums = new SubscribedForumsCollection();
            SynchronizationManager manager = new SynchronizationManager(syncStub);
            providerStub = new ForumsProviderStub();

            AppDataModel.Initialize(providerStub, manager, connectionStub, DatabaseHelper.DatabaseFileName, subscribedForums);
        }

        /// <summary>
        /// After test is complete remove all data we added from the database
        /// 
        /// Ideally, we would just be able to recreate the database but there is an odd 
        /// file locked issue that happens intermittently
        /// </summary
        [TearDown]
        public void Teardown()
        {
            DatabaseHelper.ClearDatabase();
        }


        #region Constructor Test

        /// <summary>
        /// Test that an exception is thrown if a null post is passed into the constructor
        /// </summary>
        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ConstructorThrowsIfPostIsNull()
        {
            new ThreadedPostViewModel(null, Dispatcher.CurrentDispatcher);
        }

        /// <summary>
        /// Test that an exception is thrown if a null dispatcher is passed into the constructor
        /// </summary>
        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ConstructorThrowsIfDispatcherIsNull()
        {
            new ThreadedPostViewModel(new Post(), null);
        }

        /// <summary>
        /// Test the constructor will set the public Post property and 
        /// creates an empty collection for ChildPosts
        /// </summary>
        [Test]
        public void ConstructorSetPublicProperties()
        {
            ForumDataCreator creator = new ForumDataCreator(DatabaseHelper.GetNewDataContext());
            creator.Load("RootPostTest.xml");
            ForumsOfflineStore db = DatabaseHelper.GetNewDataContext();
            Post post = db.Posts.Single(p => p.Subject == "Post1");
            ObjectRegistry.AddForum(post.ForumId);

            var postContainer = new ThreadedPostViewModel(post, Dispatcher.CurrentDispatcher);

            Assert.AreEqual(post.Id, postContainer.Post.Id);
            Assert.AreEqual(0, postContainer.ChildPosts.Count);
        }

        #endregion

        [Test]
        public void ShowChildrenAddChildPostsToCollection()
        {
            ForumDataCreator creator = new ForumDataCreator(DatabaseHelper.GetNewDataContext());
            creator.Load("RootPostTest.xml");
            ForumsOfflineStore db = DatabaseHelper.GetNewDataContext();
            
            Post parentPost = db.Posts.Single(p => p.Subject == "Post1");
            ObjectRegistry.AddForum(parentPost.ForumId);
            var container = new ThreadedPostViewModel(parentPost, Dispatcher.CurrentDispatcher);

            container.AreChildrenDisplayed = true;

            Assert.AreEqual(2, container.ChildPosts.Count);
            foreach (ThreadedPostViewModel post in container.ChildPosts)
            {
                Assert.IsTrue(db.Posts.Any(p => p == post.Post && post.AreChildrenDisplayed));
            }

        }

        /// <summary>
        /// Test that calling UpdatePosts will add new child posts to collection and update exisiting child posts
        /// at two different levels in the post hierarchy
        /// </summary>
        [Test]
        public void UpdateChildPostsAddsNewChildPostsUpdatesExistingPosts()
        {
            var creator = new ForumDataCreator(DatabaseHelper.GetNewDataContext());
            creator.Load("RootPostTest.xml");
            ForumsOfflineStore db = DatabaseHelper.GetNewDataContext();
            
            Forum forum = db.Forums.Single(f => f.DisplayName == "Forum1");
            ObjectRegistry.AddForum(forum);
            Post rootPost = db.Posts.Single(p => p.Subject == "Post1");
            var rootPostModel = new ThreadedPostViewModel(rootPost, Dispatcher.CurrentDispatcher);
            rootPostModel.AreChildrenDisplayed = true; // populate children
            var firstChildModel = rootPostModel.ChildPosts[0];

            // Update Posts
            Post detachedRootPost = ForumObjectHelper.Duplicate(rootPost);
            string oldRootPostSubject = detachedRootPost.Subject;
            detachedRootPost.Subject = "New Subject 1";
            DatabaseHelper.UpdateDatabaseWithItem(detachedRootPost);
            ObjectRegistry.UpdatePost(forum.Id, detachedRootPost.Id);

            Post detachedFirstChild = ForumObjectHelper.Duplicate(firstChildModel.Post);
            string oldFirstChildSubject = detachedFirstChild.Subject;
            detachedFirstChild.Subject = "New Subject 2";
            DatabaseHelper.UpdateDatabaseWithItem(detachedFirstChild);
            ObjectRegistry.UpdatePost(forum.Id, detachedFirstChild.Id);

            // New Posts
            Post newRootPostChild = ForumObjectHelper.MakeNewPost("NewPost", forum.Id, rootPost.ThreadId, rootPost.Id);
            DatabaseHelper.InsertItemIntoDatabase(newRootPostChild);
            rootPost.OnChildAdded(newRootPostChild.Id);

            Post newFirstChildPostChild = ForumObjectHelper.MakeNewPost("FirstChildChild", forum.Id, rootPost.ThreadId, firstChildModel.Post.Id);
            DatabaseHelper.InsertItemIntoDatabase(newFirstChildPostChild);
            firstChildModel.Post.OnChildAdded(newFirstChildPostChild.Id);

            // Testing root level insertion
            Assert.AreEqual(3, rootPostModel.ChildPosts.Count);
            foreach (ThreadedPostViewModel post in rootPostModel.ChildPosts)
            {
                Assert.IsTrue(db.Posts.Any(p => p == post.Post));
            }

            // Testing first child level insertion
            Assert.IsTrue(firstChildModel.HasChildren);
            Assert.AreEqual(1, firstChildModel.ChildPosts.Count);

            foreach (ThreadedPostViewModel post in firstChildModel.ChildPosts)
            {
                Assert.IsTrue(db.Posts.Any(p => p == post.Post));
            }

            // Testing Root Post Update
            Assert.IsTrue(db.Posts.Any(p => p.Subject == detachedRootPost.Subject));
            Assert.IsFalse(db.Posts.Any(p => p.Subject == oldRootPostSubject));

            // Testing first child update
            Assert.IsTrue(db.Posts.Any(p => p.Subject == detachedFirstChild.Subject));
            Assert.IsFalse(db.Posts.Any(p => p.Subject == oldFirstChildSubject));
        }



    }
}
